
#include <iostream>
#include <stdio.h>
#include <libXml/tree.h>

#include "AnimatedEntity.h"
#include "hRandom.h"
#include "SB.h"
#include "Painter.h"
#include "TextureManager.h"

extern "C" const char* getFilePath(const char* fileName);

std::map< std::string, sAnimatedEntityData> AnimatedEntity::datas;

AnimatedEntity::AnimatedEntity(std::string _entityName, Vector2 position)
: RenderableEntity::RenderableEntity(0, position)
{
    entityName = _entityName;
    actionState = "idle";
    actionTimer = 0.0f;
    currentTextureId = 0;
    currentFrame = 0;
    paintMask = Vector2(1,1);
    animationSpeed = 1.0f;
    
    // load actions and textures if they havent been readen yet
    if (datas.find(entityName) == datas.end())
    {
        readXML(entityName);
    }
    
    // assign the pointers for this instance
    actions = datas[entityName].actions;
    animatedTextures = datas[entityName].animatedTextures;
    
    m_vSize = Vector2(animatedTextures.at(0).frameWidth, animatedTextures.at(0).frameHeight);
    m_vButtonSize = m_vSize;
    
    currentFrame = actions["idle"]->initialFrame;
    if (datas[entityName].idleOffset)
    {
        currentFrame = hRandom::getRandomInt(actions["idle"]->initialFrame, actions["idle"]->endFrame);
        float frameTime = 1.0f / actions["idle"]->FPS;
        actionTimer = frameTime * (currentFrame - actions["idle"]->initialFrame);
    }
    
//    m_vCenter = Vector2(animatedTextures[0].frameWidth * 0.5, animatedTextures[0].frameHeight * 0.5);
    m_vCenter = Vector2(0.5, 0.5);
    
    avoidDelete = false;
    
    playAction("idle");
}

void AnimatedEntity::unload()
{
    datas.clear();
}

Vector2 AnimatedEntity::getFrameSize()
{
    return Vector2(animatedTextures[0].frameWidth, animatedTextures[0].frameHeight);
}

std::string AnimatedEntity::getCurrentAction()
{
    return actionState;
}

void AnimatedEntity::playAction(std::string newAction, bool forcePlay, float _animationSpeed)
{
    if ((actionState == "die" || actionState == newAction) && !forcePlay) return;
    
    animationSpeed = _animationSpeed;
    
    if (actions[newAction]->playRandom)
    {
        nextRandomActionTime = hRandom::getRandomInt(actions[newAction]->playRandomMin, actions[newAction]->playRandomMax);
    }
    
    // see if a transition exists
    std::map<std::string, AnimationAction*>::iterator it;
    for ( it=actions.begin() ; it != actions.end(); it++ )
    {
        if (it->second->from == actionState && it->second->to == newAction)
        {
            it->second->playAtEnd = newAction;
            newAction = it->second->name;
            break;
        }
    }
    
    actionState = newAction;
    actionTimer = 0.0f;
}

// reads xml and loads textures and actions for this animated entity. Can be called from outside this class
void AnimatedEntity::readXML(std::string entityName)
{
    xmlDocPtr doc = NULL;
    std::string fileName = "/" + entityName + ".xml";
    std::string fullName = getFilePath(fileName.c_str());
    doc = xmlReadFile(fullName.c_str(), "UTF8", 0);
	if (doc == NULL) {
		return;
	}
	xmlNode *root = NULL;
	root = xmlDocGetRootElement(doc);

    sAnimatedEntityData data;
    if(xmlHasProp(root, (xmlChar*)"idleOffset"))
       data.idleOffset = std::string( (const char*) xmlGetProp(root, (xmlChar*)"idleOffset") ) == "true";
    else
       data.idleOffset = false;
    
    int textureNumber = 0;
    
    //AnimatedTextures
    xmlNode* nodeAnimatedTexture = root->children;
    while(nodeAnimatedTexture)
    {
        if(nodeAnimatedTexture->type != XML_ELEMENT_NODE)
        {
            nodeAnimatedTexture = nodeAnimatedTexture->next;
            continue;
        }
        
        std::string name = (char*) nodeAnimatedTexture->name;
        sAnimatedTexture animTex;
            
        animTex.id = textureNumber;
        if(xmlHasProp(nodeAnimatedTexture, (xmlChar*)"name"))
            animTex.name = (const char*) xmlGetProp(nodeAnimatedTexture, (xmlChar*)"name");
        
        animTex.texture = (Texture*) TextureManager::instance().getTextureByName(animTex.name);
        animTex.columns = atoi((const char*) xmlGetProp(nodeAnimatedTexture, (xmlChar*)"columns"));
        animTex.rows = atoi((const char*) xmlGetProp(nodeAnimatedTexture, (xmlChar*)"rows"));
        animTex.frameWidth = atof((const char*) xmlGetProp(nodeAnimatedTexture, (xmlChar*)"frameWidth"));
        animTex.frameHeight = atof((const char*) xmlGetProp(nodeAnimatedTexture, (xmlChar*)"frameHeight"));
        animTex.frameWidthUV = animTex.frameWidth / animTex.texture->getSize().x;
        animTex.frameHeightUV = animTex.frameHeight / animTex.texture->getSize().y;
        
        data.animatedTextures.push_back(animTex);
        
        //Actions
        xmlNode* nodeAction = nodeAnimatedTexture->children;
        while(nodeAction)
        {
            if(nodeAction->type != XML_ELEMENT_NODE)
            {
                nodeAction = nodeAction->next;
                continue;
            }
            
            AnimationAction* animAction = new AnimationAction();
            
            animAction->textureId = textureNumber;
            animAction->name = (const char*) xmlGetProp(nodeAction, (xmlChar*)"name");
            
            if(xmlHasProp(nodeAction, (xmlChar*)"from"))
                animAction->from = (const char*) xmlGetProp(nodeAction, (xmlChar*)"from");
            else
                animAction->from = "";
            
            if(xmlHasProp(nodeAction, (xmlChar*)"to"))
                animAction->from = (const char*) xmlGetProp(nodeAction, (xmlChar*)"to");
            else
                animAction->from = "";
            
            animAction->initialFrame = atoi((const char*) xmlGetProp(nodeAction, (xmlChar*)"initialFrame")) - 1;
            animAction->endFrame = atoi((const char*) xmlGetProp(nodeAction, (xmlChar*)"endFrame")) - 1;
            
            if(xmlHasProp(nodeAction, (xmlChar*)"FPS"))
                animAction->FPS = atoi((const char*) xmlGetProp(nodeAction, (xmlChar*)"FPS"));
            else
                animAction->FPS = 30;
            
            if(xmlHasProp(nodeAction, (xmlChar*)"loops"))
                animAction->loops = std::string( (const char*) xmlGetProp(nodeAction, (xmlChar*)"loops") ) == "true";
            else
                animAction->loops = false;
            
            if(xmlHasProp(nodeAction, (xmlChar*)"playAtEnd"))
                animAction->playAtEnd = (const char*) xmlGetProp(nodeAction, (xmlChar*)"playAtEnd");
            else
                animAction->playAtEnd = "";
            
            if(xmlHasProp(nodeAction, (xmlChar*)"playRandom"))
            {
                animAction->playRandom = std::string( (const char*) xmlGetProp(nodeAction, (xmlChar*)"playRandom") ) == "true";
                if(animAction->playRandom)
                {
                    animAction->playRandomMin = atof((const char*) xmlGetProp(nodeAction, (xmlChar*)"playRandomMin"));
                    animAction->playRandomMax = atof((const char*) xmlGetProp(nodeAction, (xmlChar*)"playRandomMax"));
                    
                    xmlNode* nodeRandomActions = nodeAction->children;
                    while(nodeRandomActions)
                    {
                        if(nodeRandomActions->type != XML_ELEMENT_NODE)
                        {
                            nodeRandomActions = nodeRandomActions->next;
                            continue;
                        }

                        sRandomAction randomAction;
                        randomAction.name = (const char*) xmlGetProp(nodeRandomActions, (xmlChar*)"name");
                        if(xmlHasProp(nodeAction, (xmlChar*)"probability"))
                            randomAction.probability = atof((const char*) xmlGetProp(nodeAction, (xmlChar*)"probability"));
                        else
                            randomAction.probability = 1.0f;
                        
                        animAction->randomAction.push_back(randomAction);
                        
                        nodeRandomActions = nodeRandomActions->next;
                    }
                }
            }
            else
                animAction->playRandom = false;
            
            animAction->initialize();
            
            data.actions[animAction->name] = animAction;
            
            nodeAction = nodeAction->next;
        }
        
        nodeAnimatedTexture = nodeAnimatedTexture->next;
        textureNumber++;
    }
    
    datas[entityName] = data;
	
	xmlFreeDoc(doc);
}

void AnimatedEntity::die()
{
    playAction("die");
    entityState = Dying;
}

void AnimatedEntity::update()
{
    RenderableEntity::update();
    
    actionTimer += SB::dt * animationSpeed;
    
    AnimationAction* action = actions[actionState];
    currentTextureId = action->textureId;
    
    // play a the random action if necessary
    if (action->playRandom)
    {
        nextRandomActionTime -= SB::dt;
        if (nextRandomActionTime < 0.0f)
        {
            float random = hRandom::getRandom();
            float accumulatedProbability = 0.0f;
            for (int i = 0; i < action->randomAction.size(); ++i)
            {
                accumulatedProbability += action->randomAction[i].probability;
                if (random < accumulatedProbability)
                {
                    playAction(action->randomAction[i].name);
                    action = actions[actionState];
                    break;
                }
            }
        }
    }
    
    // get which would have to be the current frame
    float frameTime = 1.0f / action->FPS;
    currentFrame = (int)(actionTimer / frameTime);
    // at the end of the animation...
    if (currentFrame >= action->totalFrames)
    {
        // we want to take the time passed since the animation ended (like an error) and keep it, then update the current frame (possibly will be 0 again)
        actionTimer -= action->totalFrames * frameTime;
        currentFrame = (int)(actionTimer / frameTime);
        // if the action doesnt loop, go to idle
        if (!action->loops)
        {
            // if the animation played was die, delete the entity
            if (actionState == "die")
            {
                if (avoidDelete)
                {
                    m_bRender = false;
                }
                else
                {
//                    requestDelete();
                }
            }
            else if (action->playAtEnd.compare("") != 0)
            {
                playAction(action->playAtEnd);
            }
            else
            {
                playAction("idle");
            }
        }
    }
    
    // calculated the current frame relative to 0, we need to transpose to the real animations initial frame
    // we dont use the local variable action because maybe the action state has changed, and the action initial frame is another
    currentFrame += actions[actionState]->initialFrame;
}

void AnimatedEntity::render()
{
    if (!m_bRender) return;
    
    currentTextureId = actions[actionState]->textureId;
    
    int columns = animatedTextures[currentTextureId].columns;
    int x = currentFrame % columns;
    int y = (int)(currentFrame / columns);
    
    float frameWidthUV = animatedTextures[currentTextureId].frameWidthUV;
    float frameHeightUV = animatedTextures[currentTextureId].frameHeightUV;
    
    Vector2 initialUVs = Vector2( frameWidthUV * x, frameHeightUV * y);
//    Vector2 endingUVs = Vector2( frameWidthUV * (x + paintMask.x), (frameHeightUV) * (y + paintMask.y));
    Vector2 endingUVs = Vector2( frameWidthUV * (x + 1), frameHeightUV * (y + 1));
    
    if (m_bFlipHorizontal)
    {
        float temp = endingUVs.x;
        endingUVs.x = initialUVs.x;
        initialUVs.x = temp;
    }
    
    if (m_bFlipVertical)
    {
        float temp = endingUVs.y;
        endingUVs.y = initialUVs.y;
        initialUVs.y = temp;
    }
    
//    Matrix mat = Matrix.Multiply(Matrix.CreateScale(paintMask.X, paintMask.Y, 1.0f), worldMatrix);
    
    sAnimatedTexture anim = animatedTextures[currentTextureId];
    
    Painter::drawTexturedQad(m_vPosition.x, m_vPosition.y, m_vCenter.x, m_vCenter.y, anim.frameWidth * m_vScale.x, anim.frameHeight * m_vScale.y, initialUVs.x, initialUVs.y, endingUVs.x, endingUVs.y, m_fRotation, animatedTextures[currentTextureId].texture, getColor());
}

//void AnimatedEntity::deleteEntity()
//{
//    RenderableEntity::deleteEntity();
//}
//
//void AnimatedEntity::requestDelete(bool force)
//{
//    if (avoidDelete && !force) return;
//    
//    entityState = tEntityState.ToDelete;
//}
